namespace FluentMvc
{
    using System;
    using System.Linq.Expressions;
    using System.Web.Mvc;
    using Validations;

    public abstract class RuleConfigurator<TModel, TProperty, TChild>
        where TModel : class
        where TChild : RuleConfigurator<TModel, TProperty, TChild>
    {
        protected TChild Child;
        protected readonly ModelContext<TModel> ModelContext;
        protected readonly Func<TModel, TProperty> PropertySelector;
        protected readonly string PropertyName;

        internal RuleConfigurator(Expression<Func<TModel, TProperty>> selector, ModelContext<TModel> context)
        {
            ModelContext = context;
            PropertySelector = selector.Compile();
            PropertyName = new ExpressionHandler().GetFullPropertyName(selector);
        }

        public TChild Required(Action<RuleInitializer<TModel, TProperty>> configuration = null)
        {
            ModelContext.GetSettings(PropertyName).ValidationSettings.Add(RunConfiguration(configuration, new ValidationSetting<TModel>
            {
                Rule = new RequiredValidator(),
                PropertyName = PropertyName
            }));
            return Child;
        }

        public TChild EqualTo(Expression<Func<TModel, TProperty>> selector, Action<RuleInitializer<TModel, TProperty>> configuration = null)
        {
            var rule = new ValidationSetting<TModel>
            {
                Rule = new EqualToValidator { OtherPropertyName = new ExpressionHandler().GetPropertyName(selector) },
                PropertyName = PropertyName
            };
            ModelContext.GetSettings(PropertyName).ValidationSettings.Add(RunConfiguration(configuration, rule));
            return Child;
        }

        public TChild CustomValidator(Func<TModel, bool> predicate, Action<RuleInitializer<TModel, TProperty>> configuration = null)
        {
            var rule = new ValidationSetting<TModel>
            {
                CustomValidation = predicate,
                PropertyName = PropertyName
            };
            ModelContext.GetSettings(PropertyName).ValidationSettings.Add(RunConfiguration(configuration, rule));
            return Child;
        }

        public TChild CustomValidator<TValidationRule>(TValidationRule validationRule, Action<RuleInitializer<TModel, TProperty>> configuration = null) where TValidationRule : FluentValidator
        {
            var rule = new ValidationSetting<TModel>
            {
                Rule = validationRule,
                PropertyName = PropertyName
            };
            ModelContext.GetSettings(PropertyName).ValidationSettings.Add(RunConfiguration(configuration, rule));
            return Child;
        }

        public TChild Remote<TController>(Expression<Func<TController, Func<TProperty,JsonResult>>> action) where TController : ControllerBase, new()
        {
            return Remote<TController, TProperty>(action);
        }

        public TChild Remote<TController>(Expression<Func<TController, Func<TModel, JsonResult>>> action, params Expression<Func<TModel, object>>[] additionalFields) where TController : ControllerBase, new()
        {
            return Remote<TController, TModel>(action, additionalFields);
        }

        public TChild BeforeValidate(Action<FluentValidationContext> action)
        {
            ModelContext.GetSettings(PropertyName).BeforeValidate = action;
            return Child;
        }

        public TChild AfterValidate(Action<AfterValidateArgs> action)
        {
            ModelContext.GetSettings(PropertyName).AfterValidate = action;
            return Child;
        }

        private TChild Remote<TController, TParameter>(Expression<Func<TController, Func<TParameter, JsonResult>>> action, params Expression<Func<TModel, object>>[] additionalFields) where TController : ControllerBase
        {
            var type = typeof(TController);
            var areaName = GetAreaName(type.Namespace);
            var controllerName = type.Name.Replace("Controller", "");
            var actionType = new ExpressionHandler().GetMethod(action);
            var actionName = actionType.Name;
            var validator = new RemoteValidationSettings
            {
                ControllerType = type,
                PropertyName = PropertyName,
                Area = areaName,
                Controller = controllerName,
                Action = actionName,
                MethodInfo = actionType
            };
            foreach (var additionalField in additionalFields)
                validator.AdditionalFields.Add(new ExpressionHandler().GetFullPropertyName(additionalField));
            ModelContext.GetSettings(PropertyName).RemoteValidators.Add(validator);
            return Child;
        }

        private static string GetAreaName(string @namespace)
        {
            if (!@namespace.Contains("Areas"))
                return "";
            var areaStartIndex = @namespace.IndexOf("Areas") + 6;
            var areaEndIndex = @namespace.IndexOf(".", areaStartIndex);
            return @namespace.Substring(areaStartIndex, areaEndIndex - areaStartIndex);
        }

        internal ValidationSetting<TModel> RunConfiguration(Action<RuleInitializer<TModel, TProperty>> configuration, ValidationSetting<TModel> setting)
        {
            if (configuration != null)
                configuration(new RuleInitializer<TModel, TProperty>(setting));
            return setting;
        }
    }
}